use crate::ast::*;
use crate::error::{CompilerError, Result};
use inkwell::context::Context;
use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicMetadataValueEnum, PointerValue};
use std::process::Command;

use super::LLVMBackend;

impl<'ctx> LLVMBackend<'ctx> {
    /// Generate a native executable for the compiled module.
    pub fn generate_executable_with_target(
        &self,
        output_path: &str,
        target_config: &crate::TargetConfig,
    ) -> Result<()> {
        self.try_generate_native_executable(output_path, Some(target_config))
    }

    /// Try to generate native executable using llc and gcc
    fn try_generate_native_executable(
        &self,
        output_path: &str,
        target_config: Option<&crate::TargetConfig>,
    ) -> Result<()> {
        use crate::parser::Parser;
        use std::fs;

        // Create temporary directory
        let temp_dir = std::env::temp_dir().join("nrc_llvm_compile");
        fs::create_dir_all(&temp_dir)?;

        // Use module name to create unique filenames for IR and object files
        let module_name = self.module.get_name().to_str().unwrap();

        // Define file paths for main module
        let ir_file = temp_dir.join(format!("{}.ll", module_name));
        let obj_file = temp_dir.join(format!("{}.o", module_name));

        // IMPORTANT: Remove old files to prevent stale cache issues
        let _ = fs::remove_file(&ir_file);
        let _ = fs::remove_file(&obj_file);

        // Write LLVM IR to file
        fs::write(&ir_file, self.module.print_to_string().to_string())?;

        // Compile main module to object file
        self.compile_to_object(&ir_file, &obj_file, target_config)?;

        // Collect all object files (main + imported modules)
        let mut object_files = vec![obj_file.clone()];

        // Compile imported modules
        for module_path in &self.imported_module_paths {
            // Read and parse the module
            let module_source = fs::read_to_string(module_path).map_err(|e| {
                CompilerError::internal(&format!("Failed to read module '{}': {}", module_path, e))
            })?;

            let mut parser = Parser::new(module_source, Some(module_path.clone()));
            let module_program = parser.parse_program().map_err(|e| {
                CompilerError::internal(&format!("Failed to parse module '{}': {}", module_path, e))
            })?;

            // Extract module name from path (e.g., "/path/to/mathlib.nr" -> "mathlib")
            let module_file_name = std::path::Path::new(module_path)
                .file_stem()
                .and_then(|s| s.to_str())
                .unwrap_or("module");

            // Create a new LLVM backend for this module
            let module_context = Context::create();
            let mut module_backend = LLVMBackend::new(&module_context, module_file_name)?;

            // Set the source directory for this module (same as the module's directory)
            let module_dir = std::path::Path::new(module_path)
                .parent()
                .map(|p| p.to_path_buf())
                .unwrap_or_else(|| std::path::PathBuf::from("."));
            module_backend.set_source_directory(module_dir);

            // Generate code for the module
            module_backend.generate_program(&module_program)?;

            // Define file paths for this module
            let module_ir_file = temp_dir.join(format!("{}.ll", module_file_name));
            let module_obj_file = temp_dir.join(format!("{}.o", module_file_name));

            // Remove old files
            let _ = fs::remove_file(&module_ir_file);
            let _ = fs::remove_file(&module_obj_file);

            // Write LLVM IR for this module
            fs::write(
                &module_ir_file,
                module_backend.module.print_to_string().to_string(),
            )?;

            // Compile this module to object file
            self.compile_to_object(&module_ir_file, &module_obj_file, target_config)?;

            // Add to object files list
            object_files.push(module_obj_file);
        }

        // Link all object files together
        self.link_object_files(&object_files, output_path, target_config)?;

        Ok(())
    }

    /// Compile LLVM IR file to object file
    fn compile_to_object(
        &self,
        ir_file: &std::path::PathBuf,
        obj_file: &std::path::PathBuf,
        target_config: Option<&crate::TargetConfig>,
    ) -> Result<()> {
        // Get llc command based on target
        let llc_cmd = if let Some(target) = target_config {
            target.get_llc_command()
        } else {
            "llc-15"
        };

        let mut llc_cmd_builder = Command::new(llc_cmd);
        llc_cmd_builder
            .arg("-filetype=obj")
            .arg(ir_file)
            .arg("-o")
            .arg(obj_file);

        // Add target triple if specified
        if let Some(target) = target_config {
            llc_cmd_builder
                .arg("-mtriple")
                .arg(&target.to_llvm_triple());
        }

        let llc_result = llc_cmd_builder
            .output()
            .map_err(|e| CompilerError::internal(&format!("Failed to execute llc: {}", e)))?;

        if !llc_result.status.success() {
            let stderr = String::from_utf8_lossy(&llc_result.stderr);
            let stdout = String::from_utf8_lossy(&llc_result.stdout);
            eprintln!("llc failed:");
            eprintln!("stdout: {}", stdout);
            eprintln!("stderr: {}", stderr);
            return Err(CompilerError::internal(&format!(
                "llc failed with exit code: {:?}",
                llc_result.status.code()
            )));
        }

        Ok(())
    }

    /// Link object files into executable
    fn link_object_files(
        &self,
        object_files: &[std::path::PathBuf],
        output_path: &str,
        target_config: Option<&crate::TargetConfig>,
    ) -> Result<()> {
        // Get linker command
        let linker_cmd = if let Some(target) = target_config {
            target.get_linker_command()
        } else {
            "gcc"
        };

        let mut link_cmd_builder = Command::new(linker_cmd);

        // Add all object files
        for obj_file in object_files {
            link_cmd_builder.arg(obj_file);
        }

        link_cmd_builder.arg("-o").arg(output_path);

        // Platform-specific linking flags
        if let Some(target) = target_config {
            match target.os.as_str() {
                "linux" => {
                    link_cmd_builder.arg("-lc").arg("-lm").arg("-no-pie");
                }
                "darwin" => {
                    link_cmd_builder.arg("-lc").arg("-lm");
                }
                _ => {}
            }
        } else {
            // Default: add standard libraries and disable PIE
            link_cmd_builder.arg("-lc").arg("-lm").arg("-no-pie");
        }

        let link_result = link_cmd_builder
            .output()
            .map_err(|e| CompilerError::internal(&format!("Failed to execute linker: {}", e)))?;

        if !link_result.status.success() {
            let stderr = String::from_utf8_lossy(&link_result.stderr);
            let stdout = String::from_utf8_lossy(&link_result.stdout);
            eprintln!("Linker failed:");
            eprintln!("stdout: {}", stdout);
            eprintln!("stderr: {}", stderr);
            return Err(CompilerError::internal(&format!(
                "Linker failed with exit code: {:?}",
                link_result.status.code()
            )));
        }

        Ok(())
    }

    /// Add extern function declarations for imported functions
    /// Returns a list of imported module paths that need to be compiled
    pub(super) fn add_imported_function_declarations(
        &mut self,
        program: &Program,
    ) -> Result<Vec<String>> {
        use crate::semantic::module_resolver::ModuleResolver;
        use std::collections::HashSet;

        // Create a module resolver to load imported modules
        // Use source directory instead of current directory
        let mut module_resolver = ModuleResolver::new(self.source_directory.clone())?;

        // Track imported module paths (use HashSet to avoid duplicates)
        let mut imported_module_paths_set = HashSet::new();

        // Find all import declarations in the program
        for declaration in &program.declarations {
            if let Declaration::Import(import_decl) = declaration {
                // Resolve the import
                module_resolver.resolve_import(import_decl)?;

                // Get the module path
                let module_path = module_resolver.resolve_module_path(&import_decl.path)?;

                // Add to set (automatically handles duplicates)
                imported_module_paths_set.insert(module_path.clone());

                // Get the loaded module
                if let Some(module) = module_resolver.get_module(&module_path) {
                    // Handle named imports
                    if !import_decl.named_imports.is_empty() {
                        // import { add, subtract } from "./mathlib.nr"
                        // import { add as plus } from "./mathlib.nr"
                        for item in &import_decl.named_imports {
                            if let Some(export_info) = module.exports.get(&item.name) {
                                if let Declaration::Function(func_decl) = &export_info.declaration {
                                    // Always add the original function as extern
                                    self.add_extern_function_declaration(func_decl)?;

                                    // If there's an alias, create a wrapper function
                                    if let Some(alias) = &item.alias {
                                        self.create_function_alias(func_decl, alias)?;
                                    }
                                }
                            }
                        }
                    } else {
                        // import mathlib from "..." or import * as mathlib from "..."
                        // Import all exported functions
                        for (_name, export_info) in &module.exports {
                            if let Declaration::Function(func_decl) = &export_info.declaration {
                                self.add_extern_function_declaration(func_decl)?;
                            }
                        }
                    }
                }
            }
        }

        // Convert HashSet to Vec for return
        let imported_module_paths: Vec<String> = imported_module_paths_set.into_iter().collect();
        Ok(imported_module_paths)
    }

    /// Add an extern function declaration to the LLVM module
    fn add_extern_function_declaration(&mut self, func: &FunctionDecl) -> Result<()> {
        // Skip if function already exists in function_map
        let symbol_name = self.function_symbol_name(func);
        if self.function_map.contains_key(&symbol_name) {
            return Ok(());
        }

        // Get parameter types
        let param_types: Vec<BasicTypeEnum> = func
            .parameters
            .iter()
            .map(|param| self.nrc_type_to_llvm_type(&Some(param.param_type.clone())))
            .collect::<Result<Vec<_>>>()?;

        // Convert to metadata types
        let metadata_types: Vec<BasicMetadataTypeEnum> =
            param_types.iter().map(|t| (*t).into()).collect();

        // Create function type - handle void return type specially
        let function = if func.return_type.is_none() {
            // Void function
            let function_type = self.context.void_type().fn_type(&metadata_types, false);
            self.module.add_function(&symbol_name, function_type, None)
        } else {
            // Function with return value
            let return_type = self.nrc_type_to_llvm_type(&func.return_type)?;
            let function_type = return_type.fn_type(&metadata_types, false);
            self.module.add_function(&symbol_name, function_type, None)
        };

        // Set parameter names
        for (i, param) in func.parameters.iter().enumerate() {
            if let Some(llvm_param) = function.get_nth_param(i as u32) {
                llvm_param.set_name(&param.name);
            }
        }

        // Store function in map (this is an extern declaration, no body)
        self.function_map.insert(symbol_name, function);

        Ok(())
    }

    /// Create a function alias (wrapper function that calls the original)
    /// This is used for import aliases like: import { add as plus } from "./math.nr"
    fn create_function_alias(
        &mut self,
        original_func: &FunctionDecl,
        alias_name: &str,
    ) -> Result<()> {
        let original_symbol_name = self.function_symbol_name(original_func);
        let mut alias_decl = original_func.clone();
        alias_decl.name = alias_name.to_string();
        let alias_symbol_name = self.function_symbol_name(&alias_decl);

        // Skip if alias already exists
        if self.function_map.contains_key(&alias_symbol_name) {
            return Ok(());
        }

        // Get the original function from the module
        let original_fn = self
            .function_map
            .get(&original_symbol_name)
            .ok_or_else(|| {
                CompilerError::internal(&format!(
                    "Original function '{}' not found when creating alias '{}'",
                    original_func.name, alias_name
                ))
            })?;

        // Create the alias function with the same signature
        let param_types: Vec<BasicTypeEnum> = original_func
            .parameters
            .iter()
            .map(|param| self.nrc_type_to_llvm_type(&Some(param.param_type.clone())))
            .collect::<Result<Vec<_>>>()?;

        let param_meta_types: Vec<BasicMetadataTypeEnum> =
            param_types.iter().map(|t| (*t).into()).collect();

        let return_type = if let Some(ret_ty) = &original_func.return_type {
            self.nrc_type_to_llvm_type(&Some(ret_ty.clone()))?
        } else {
            self.context.i32_type().as_basic_type_enum()
        };

        let fn_type = return_type.fn_type(&param_meta_types, false);
        let alias_fn = self.module.add_function(&alias_symbol_name, fn_type, None);

        // Create function body that just calls the original function
        let entry = self.context.append_basic_block(alias_fn, "entry");
        self.builder.position_at_end(entry);

        // Collect parameters
        let args: Vec<BasicMetadataValueEnum> = alias_fn
            .get_params()
            .iter()
            .map(|param| (*param).into())
            .collect();

        // Call the original function
        let call_result = self
            .builder
            .build_call(*original_fn, &args, "call")
            .map_err(|e| CompilerError::internal(&format!("Failed to build call: {:?}", e)))?;

        // Return the result
        if let Some(result_value) = call_result.try_as_basic_value().left() {
            self.builder
                .build_return(Some(&result_value))
                .map_err(|e| {
                    CompilerError::internal(&format!("Failed to build return: {:?}", e))
                })?;
        } else {
            self.builder.build_return(None).map_err(|e| {
                CompilerError::internal(&format!("Failed to build return: {:?}", e))
            })?;
        }

        // Store alias in function map
        self.function_map.insert(alias_symbol_name, alias_fn);

        Ok(())
    }

    /// Call the init method for a struct instance
    pub fn call_init_method(
        &mut self,
        struct_decl: &StructDecl,
        struct_ptr: PointerValue<'ctx>,
    ) -> Result<()> {
        if let Some(init_method) = struct_decl.methods.iter().find(|m| m.name == "init") {
            let mut candidates = vec![self.function_symbol_name(init_method)];
            if let Some(first_param) = init_method.parameters.get(0) {
                if let Type::Reference(reference_type) = &first_param.param_type {
                    if let Type::Struct(struct_type) = &*reference_type.referenced_type.clone() {
                        let qualified = format!("{}.{}", struct_type.name, init_method.name);
                        if qualified != candidates[0] {
                            candidates.push(qualified);
                        }
                    }
                }
            }
            if candidates.len() == 1 {
                let qualified = format!("{}.{}", struct_decl.name, init_method.name);
                if qualified != candidates[0] {
                    candidates.push(qualified);
                }
            }

            for name in candidates {
                if let Some(function) = self.function_map.get(&name) {
                    let args = vec![struct_ptr.into()];
                    let _ = self.builder.build_call(*function, &args, "init_call");
                    break;
                }
            }
        }

        Ok(())
    }
}
